package com.geekhale.validate.support;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.TypeUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.MapBindingResult;
import org.springframework.validation.Validator;

import javax.validation.Valid;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Component
@Aspect
@Slf4j
public class MethodValidatePostProcessor {

    private Set<Validator> validators = new HashSet<>();

    @Autowired
    public void setValidators(List<Validator> validators){
        validators.forEach(validator -> {
            if (validator instanceof ValidatorAdapter){
                Validator target = ((ValidatorAdapter) validator).getTarget();
                this.validators.add(target);
            }else {
                this.validators.add(validator);
            }
        });
    }

    @Pointcut("@annotation(com.geekhale.validate.support.MethodValidate)")
    public void pointcut() {
    }

    @Before(value = "pointcut()")
    public void validateSign(JoinPoint joinPoint) {
        Object[] arguments = joinPoint.getArgs();
        Errors errors = new MapBindingResult(new HashMap<>(), "test");
        for (Object param : arguments){
            validateFor(param, errors);
        }
        log.info("{}", errors);

        if (errors.hasErrors()){
            String msg = errors.getAllErrors().stream()
                    .map(e -> e.getDefaultMessage())
                    .collect(Collectors.joining(","));
            throw new RuntimeException(msg);
        }
    }

    private void validateFor(Object param, Errors errors) {
        if (param == null){
            return;
        }
        this.validators.stream()
                .filter(validator -> validator.supports(param.getClass()))
                .forEach(validator -> {
                    validator.validate(param, errors);
                });
        ReflectionUtils.doWithFields(param.getClass(), new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                if (needValidate(field)){
                    field.setAccessible(true);
                    Object value = field.get(param);
                    if (value != null){
                        validateFor(value, errors);
                    }
                }
            }
        });
    }
    private Set<Class> singelTypes = createSingleTypes();

    private Set<Class> createSingleTypes() {
        Set<Class> singelTypes = new HashSet<Class>();
        singelTypes.add(String.class);
        singelTypes.add(Integer.class);
        singelTypes.add(Long.class);
        singelTypes.add(Date.class);
        singelTypes.add(Integer.TYPE);
        singelTypes.add(Long.TYPE);
        return singelTypes;
    }

    private boolean needValidate(Field field) {
        if (AnnotatedElementUtils.hasAnnotation(field, Valid.class)){
            return false;
        }
        Class<?> type = field.getType();
        if (singelTypes.contains(type)){
            return false;
        }
        return true;

    }
//    @Override
//    public void afterPropertiesSet() {
//        Pointcut pointcut = new AnnotationMatchingPointcut(MethodValidate.class, true);
//        this.advisor = new DefaultPointcutAdvisor(pointcut, );
//    }
}
